#include "pch.h"
#include "Utils.h"

#include <windows.h>
#include <mmsystem.h>
#include <sys/timeb.h>

int64_t Utils::TimeNanos() {

	int64_t ticks;
	static volatile LONG last_timegettime = 0;
	static volatile int64_t num_wrap_timegettime = 0;
	volatile LONG* last_timegettime_ptr = &last_timegettime;
	DWORD now = timeGetTime();
	// Atomically update the last gotten time
	DWORD old = InterlockedExchange(last_timegettime_ptr, now);
	if (now < old) {
		// If now is earlier than old, there may have been a race between threads.
		// 0x0fffffff ~3.1 days, the code will not take that long to execute
		// so it must have been a wrap around.
		if (old > 0xf0000000 && now < 0x0fffffff) {
			num_wrap_timegettime++;
		}
	}
	ticks = now + (num_wrap_timegettime << 32);
	// TODO(deadbeef): Calculate with nanosecond precision. Otherwise, we're
	// just wasting a multiply and divide when doing Time() on Windows.
	ticks = ticks * kNumNanosecsPerMillisec;

	return ticks;
}


bool Utils::GetScreenList(DesktopCapturer::SourceList* screens,
	std::vector<std::string>* device_names /* = nullptr */) {

	BOOL enum_result = TRUE;
	for (int device_index = 0;; ++device_index) {
		DISPLAY_DEVICEW device;
		device.cb = sizeof(device);
		enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0);

		// |enum_result| is 0 if we have enumerated all devices.
		if (!enum_result)
			break;

		// We only care about active displays.
		if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
			continue;

		screens->push_back({ device_index, std::string() });
		if (device_names) {
			device_names->push_back(Utils::ToUtf8(device.DeviceName));
		}
	}
	return true;
}


bool Utils::IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key) {
	if (screen == kFullDesktopScreenId) {
		*device_key = L"";
		return true;
	}

	DISPLAY_DEVICEW device;
	device.cb = sizeof(device);
	BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0);
	if (enum_result)
		*device_key = device.DeviceKey;

	return !!enum_result;
}

DesktopRect Utils::GetFullscreenRect() {
	return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN),
		GetSystemMetrics(SM_YVIRTUALSCREEN),
		GetSystemMetrics(SM_CXVIRTUALSCREEN),
		GetSystemMetrics(SM_CYVIRTUALSCREEN));
}

DesktopRect Utils::GetScreenRect(DesktopCapturer::SourceId screen,
	const std::wstring& device_key) {
	if (screen == kFullDesktopScreenId) {
		return GetFullscreenRect();
	}

	DISPLAY_DEVICEW device;
	device.cb = sizeof(device);
	BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0);
	if (!result)
		return DesktopRect();

	// Verifies the device index still maps to the same display device, to make
	// sure we are capturing the same device when devices are added or removed.
	// DeviceKey is documented as reserved, but it actually contains the registry
	// key for the device and is unique for each monitor, while DeviceID is not.
	if (device_key != device.DeviceKey)
		return DesktopRect();

	DEVMODEW device_mode;
	device_mode.dmSize = sizeof(device_mode);
	device_mode.dmDriverExtra = 0;
	result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS,
		&device_mode, 0);
	if (!result)
		return DesktopRect();

	return DesktopRect::MakeXYWH(
		device_mode.dmPosition.x, device_mode.dmPosition.y,
		device_mode.dmPelsWidth, device_mode.dmPelsHeight);
}
